package com.tencent.qf.web.advice;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.tencent.qf.common.exception.ErrorCode;
import com.tencent.qf.common.exception.ServiceException;
import com.tencent.qf.common.log.ErrorLog;
import com.tencent.qf.common.pojo.HttpResult;
import com.tencent.qf.common.util.JsonUtils;
import com.tencent.qf.common.util.ServletUtils;
import com.tencent.qf.common.zhiyan.ZhiyanLogUtil;
import com.tencent.qf.web.service.errorlog.ApiErrorLogService;
import com.tencent.qf.web.wrap.MultiReadHttpServletRequestWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.MDC;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import static com.tencent.qf.common.constants.WebConstants.TRACE_ID;

/**
 * Copyright (C) 2022 Limited, a Tencent company. All rights reserved.
 * 全局错误处理器 ExceptionHandler如果配置了具体的异常类，则优先，没有匹配上，则则会使用Exception兜底处理
 *
 * @author p_htaozhou@tencent.com
 * @create: 2022/9/8 11:53
 */
@RestControllerAdvice(annotations = RestController.class)
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandle {
    @Resource
    private ApiErrorLogService apiErrorLogService;

    @ExceptionHandler(value = ServiceException.class)
    public HttpResult<?> serviceExceptionHandler(HttpServletRequest req, ServiceException ex) {
        ZhiyanLogUtil.sendLog(MDC.get(TRACE_ID), String.format("接口【%s】异常：%s", req.getRequestURL(), ex.getMessage()));
        log.info("[serviceExceptionHandler]", ex);
        return HttpResult.error(ex.getCode(), ex.getMessage());
    }

    /***
     * @MethodName methodArgumentNotValidException
     * @Description 拦截请求参数格式错误，返回校验里面的提示语
     * @param: ex
     * @return: com.tencent.qf.common.pojo.HttpResult<?>
     * @Author p_htaozhou@tencent.com
     * @Date 2022/9/26 10:55
     */

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public HttpResult<?> methodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException ex) {
        ZhiyanLogUtil.sendLog(MDC.get(TRACE_ID), String.format("接口【%s】异常：%s", req.getRequestURL(), ex.getMessage()));
        List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
        StringBuilder message = new StringBuilder();
        for (int i = 0; i < fieldErrors.size(); i++) {
            message.append(fieldErrors.get(i).getDefaultMessage());
            if (i < fieldErrors.size() - 1) {
                message.append(";");
            }
        }
        log.error("parameter verification error", ex);
        return HttpResult.error(ErrorCode.BAD_REQUEST.getCode(), message.toString());
    }

    /***
     * @MethodName httpMessageNotReadableException
     * @Description 请求参数格式错误
     * @param: ex
     * @return: com.tencent.qf.common.pojo.HttpResult<?>
     * @Author p_htaozhou@tencent.com
     * @Date 2022/9/29 10:50
     */

    @ExceptionHandler({HttpMessageNotReadableException.class})
    public HttpResult<?> httpMessageNotReadableException(HttpServletRequest req, HttpMessageNotReadableException ex) {
        log.error("httpMessageNotReadableException : {}", ex.getMessage());
        ZhiyanLogUtil.sendLog(MDC.get(TRACE_ID), String.format("接口【%s】异常：%s", req.getRequestURL(), ex.getMessage()));
        return HttpResult.error(ErrorCode.HTTP_MESSAGE_NOT_READABLE_ERROR);
    }

    /***
     * @MethodName defaultExceptionHandler
     * @Description 处理最终系统异常，兜底处理所有的一切  优先级最低
     * @param: req
     * @param: ex
     * @return: com.tencent.qf.common.pojo.HttpResult<?>
     * @Author p_htaozhou@tencent.com
     * @Date 2022/9/29 10:52
     */

    @ExceptionHandler(value = Exception.class)
    public HttpResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
        log.error("[defaultExceptionHandler]", ex);
        ZhiyanLogUtil.sendLog(MDC.get(TRACE_ID), String.format("接口【%s】异常：%s", req.getRequestURL(), ex.getMessage()));
        // 插入异常日志
        this.createExceptionLog(req, ex);
        // 返回 ERROR HttpResult
        return HttpResult.error(ErrorCode.INTERNAL_SERVER_ERROR);
    }

    private void createExceptionLog(HttpServletRequest req, Throwable e) {
        ErrorLog errorLog = null;
        try {
            // 初始化 errorLog
            errorLog = getExceptionLog(req, e);
            // 执行插入 errorLog
            apiErrorLogService.createApiErrorLog(errorLog);
        } catch (Throwable th) {
            log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
        }
    }

    private ErrorLog getExceptionLog(HttpServletRequest request, Throwable e) {
        try {
            request = new MultiReadHttpServletRequestWrapper(request);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        ErrorLog errorLog = new ErrorLog();
        // 设置异常字段
        errorLog.setExceptionName(e.getClass().getName());
        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
        String stackTrace = ExceptionUtils.getStackTrace(e);
        // 有的异常内容比较多，控制下长度，不超过2000
        if (StringUtils.isNotEmpty(stackTrace)) {
            errorLog.setExceptionStackTrace(stackTrace.length() > 2000 ? stackTrace.substring(0, 2000) : "");
        }
        StackTraceElement[] stackTraceElements = e.getStackTrace();
        if (stackTraceElements != null) {
            StackTraceElement stackTraceElement = stackTraceElements[0];
            errorLog.setExceptionClassName(stackTraceElement.getClassName());
            errorLog.setExceptionFileName(stackTraceElement.getFileName());
            errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
            errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
        }

        // 设置其它字段
        errorLog.setTraceId(TraceContext.traceId());
        errorLog.setRequestUrl(request.getRequestURI());
        Map<String, Object> requestParams = MapUtil.<String, Object>builder()
                .put("query", ServletUtil.getParamMap(request))
                .put("body", ServletUtil.getBody(request)).build();
        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
        errorLog.setRequestMethod(request.getMethod());
        errorLog.setUserAgent(ServletUtils.getUserAgent(request));
        errorLog.setUserIp(ServletUtil.getClientIP(request));
        errorLog.setExceptionTime(new Date());
        return errorLog;
    }
}
